package com.briup.day15;

import com.briup.sms.model.Student;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * 单列集合的使用
 */
public class CollectionTest {
    public static void main(String[] args) {
        // ArrayList和LinkedList谁添加元素更快？
        // 如果是调用add(E e)方法添加数据，那么都是从尾部添加，
        // 由于ArrayList只需要涉及到扩容等问题，并且JDK对其进行过优化，
        // 所以实际操作下来，ArrayList尾部添加元素的速度优于LinkedList；
        // 如果使用的是add(int index,E e)方法添加元素，
        // 如果index为0 或 贴近于LinkedList的头节点或尾节点，
        // 那么LinkedList添加元素的速度会优于ArrayList，
        // 下标越贴近于0，ArrayList添加数据的速度越慢；
        // 如果是从中间添加元素，那么LinkedList的速度会远慢于ArrayList，
        // 因为LinkedList要在中间位置添加元素，需要先从头节点开始遍历找到最中间的元素，
        // 然后才能调整中间元素前后节点的指向，添加的元素越多需要遍历的次数也越多，
        // 耗时是指数级增长的
        long start = System.currentTimeMillis();
        List<Integer> integers = new LinkedList<>();
        addData(integers);
        long end = System.currentTimeMillis();
        System.out.printf("共计耗时：%s毫秒%n", end - start);
        System.out.println(integers.size());
    }

    private static void addData(List<Integer> list) {
        // 存入1000000个元素所需时间
        for (int i = 0; i < 1000000; i++) {
            // 无论是哪一种List集合实现类，只要是add()方法，默认都是尾插法
//            list.add(i);
            // 使用头插法
//            list.add(0,i);
            // 在集合的中间添加元素
            int index = list.size() >> 1;
            list.add(index, i);
        }
    }

    private static void linkedListAddData() {
        // LinkedList: 底层使用的是双向链表，所以各个元素之间的在内存中的位置是不要求连续的，
        // LinkedList存储元素的个数原则上是没有上限的，只和你的内存大小相关联
        LinkedList<Object> objects = new LinkedList<>();
        // 调用add()方法给LinkedList集合添加元素，
        // 内部默认调用linkLast()方法，即从链表的尾节点处添加元素；
        objects.add(1);
        // 也可以通过调用addFirst()方法，从头节点处添加元素
        objects.addFirst(2);
        // 如果是在指定下标处添加元素，那么需要先从头节点或尾节点处
        // 开始遍历直到找到对应下标的位置，然后给其插入元素
        objects.add(1, "123");
    }

    public static void arrayListAddData() {
        // 假定我们已经知道要存储100万个学生数据到ArrayList中，
        // 如何能够最快的将数据存入？
        // 如果已经确定要存储的数据个数，那么建议直接在创建ArrayList集合对象时指定数组的长度；
        // 如此在后续添加元素的过程中就不用涉及到数组扩容和数组数据迁移的操作，从而节约数组数据赋值的时间
        long start = System.currentTimeMillis();
        ArrayList<Student> students = new ArrayList<>(1000000);
        for (int i = 0; i < 1000000; i++) {
            students.add(new Student());
        }
        long end = System.currentTimeMillis();
        System.out.printf("共计耗时：%s毫秒%n", end - start);
        System.out.println(students.size());
    }

    private static void introduce() {
         /*
         List集合中能够存储的元素为有序的、可重复的元素；
         其中有序指的是数据存入的顺序和获取的顺序是一致的，
         可重复指定是，在即可中可以存入内容一致、或者将同一个对象放入到多个位置中
        List集合中的重要实现类如下：
            ArrayList: 底层使用的是一个Object类型的数组，用于存储数据；有别于直接使用数组
            存储数据的是：1、ArrayList集合中内置了大量的api方法用于管理集合中存储的数据；
            2、ArrayList集合能够自动扩容，并且每一次扩容默认都是新容量是原本容量的1.5倍；
            如果在创建ArrayList对象时使用的是无参构造器，即没有限定数组容量，虽然在初始化时，
            会赋值一个空数组对象，但是在第一次使用add()方法添加元素时，会创建一个默认容量为10的数组，再进行元素添加；
            注意，ArrayList集合存储的数据是存在上限的，上限为数组长度的上限，Integer.MAX_VALUE - 8 = 2147483639
            LinkedList: 底层使用的是一个双向链表
            Vector:
         */
        // 当我们创建ArrayList容器对象时，如果选择使用单参构造器，那么可以指定集合内部数组的容量；
        // 如果使用的是无参构造器，那么属性数组会 先赋值一个空数组
        ArrayList<Object> objects = new ArrayList<>();
        // 使用add()方法直接添加元素，不用关心数组下标问题，并且基本上不用担心出现下标越界的问题
        // 因为在添加元素时，会先对数组的容量进行检查，查看是否需要扩容
        objects.add(123);
        // 当然在使用add(int index,E e)还是需要关心下标问题，下标必须在[0,size]之间
        objects.add(1, 1);
    }

    private static void test() {
        /*
            java单列集合都是继承于/实现于 Collection接口，
            在该接口中定义了所有的单列集合都会使用到的公共的方法，如：
                添加元素的方法，boolean add(T t);
                删除元素的方法，boolean remove(T t);
                查询集合中具体存储元素的个数的方法，int size();
                ....；
                同时Collection接口继承了Iterable接口，所以
                所有的单列集合都可以使用迭代器进行遍历
            在Collection接口下，根据单列集合不同的使用场景以及不同数据结构的实现方式，
            又分为了List、Set、Queue三个接口；
            根据不同的需要，分别在三个接口中对Collection接口进行了不同程度的增强；
            如List主要存储的数据是有序的，所以在List集合中可以使用下标来操作集合内部的元素，
            所以在List集合中又新增了如下方法：
            boolean add(int index,E o);
            E remove(int index);
            E get(int index);
            void set(int index,E e)
           set集合因为本身是无序的，所以在Set接口内没有新增任何的方法，所以在
           使用set合计添加元素时，可以直接使用Collection接口中定义的方法
         */
    }
}
